import { PNode } from "~/models/graph/node/prototype";
import { CodeBuilder } from "../pyCode/CodeBuilder";
import type { VarInit } from "~/models/data/var_init";
import type { DataType } from "~/models/types/type";
import { PyFunc } from "../pyCode/PyFunc";
import { PyVar } from "../pyCode/PyVar";
import { PyPkg } from "../pyCode/PyPkg";
import { PNodeType } from "../graph/node/type";
import { PSlot } from "../graph/slot/prototype";
import { PSlotType } from "../graph/slot/type";
import type { ITextPrograming } from "../text_programing";

export class PyBundle implements ITextPrograming {
  constructor(
    /** 包名称 */
    public name: string = '',

    /** 包显示名 */
    public title: string = '',

    public beforeAll: string = '',

    public beforeImport: string = '',
    // 使用了import合并
    // public codeImport: string = '',
    public afterImport: string = '',

    public beforeVar: string = '',
    public codeVar: string = '',
    public afterVar: string = '',

    public beforeFun: string = '',
    public codeFun: string = '',
    public afterFun: string = '',

    public afterAll: string = '',

    public extraData: VarInit[] = [],
    public extraNode: PNode[] = [],
    public typeDeclares: DataType[] = [],
  ) { }

  pkgs: PyPkg[] = []
  vars: PyVar[] = []
  funcs: PyFunc[] = []
  exposed: (PyVar | PyFunc)[] = []


  addPkg(pkg: PyPkg) {
    if (!this.pkgs.map(x => x.name).includes(pkg.name)) {
      this.pkgs.push(pkg)
    }
  }
  addVar(v: PyVar, expose = true) {
    if (!this.vars.map(x => x.name).includes(v.name)) {
      this.vars.push(v)
      this.codeVar += v.expression

      if(expose) {
        this.exposed.push(v)
      }
    }
  }
  addFunc(func: PyFunc, expose = true) {
    if (!this.funcs.map(x => x.sign.name).includes(func.sign.name)) {
      
      func.dependencies.forEach(dep => {
        if (dep instanceof PyVar) {
          this.addVar(dep, false)
        }
        else if (dep instanceof PyPkg) {
          this.addPkg(dep)
        }
        else {
          this.addFunc(dep, false)
        }
      })

      this.funcs.push(func)
      
      this.codeFun += `\n${func.expression}\n`
      
      if(expose) {
        this.exposed.push(func)
      }
    }
  }

  get partHeader(): string {
    return `# Use module: ${this.name}(${this.title})`;
  }

  get partImport(): string {
    const builder = new CodeBuilder();
    builder.add(this.beforeImport);
    builder.add(this.pkgs.length ? `import ${this.pkgs.map(x => x.name).join(', ')}` : '');
    builder.add(this.afterImport);
    return builder.build();
  }

  get partVar(): string {
    const builder = new CodeBuilder();
    builder.add(this.beforeVar);
    builder.add(this.codeVar);
    builder.add(this.afterVar);
    return builder.build();
  }

  get partFun(): string {
    const builder = new CodeBuilder();
    builder.add(this.beforeFun);
    builder.add(this.codeFun);
    builder.add(this.afterFun);
    return builder.build();
  }

  static CompileModules(modules: PyBundle[]) {
    const builder = new CodeBuilder();
    modules.forEach(m => {
      builder.add(m.partHeader);
    });
    modules.forEach(m => {
      builder.add(m.beforeAll);
    });
    modules.forEach(m => {
      builder.add(m.partImport);
    });
    modules.forEach(m => {
      builder.add(m.partVar);
      builder.add(m.partFun);
    });
    modules.forEach(m => {
      builder.add(m.afterAll);
    });

    return builder.build();
  }

  get dataDeclares(): VarInit[] {
    return this.extraData
  }

  get nodeDeclares(): PNode[] {
    return [
      ...this.exposed.filter(x => x instanceof PyFunc).map(func => {
        const funcType = func.isPure ? PNodeType.PureFunc: PNodeType.Function
        const processSlots = func.isPure ? [] : [PSlot.Preset.ProcessIn, PSlot.Preset.ProcessOut]
        return new PNode({
          type: funcType,
          name: func.sign.name,
          title: func.sign.name,
          slots: [
            ...processSlots,
            ...func.sign.params.map(param => new PSlot(param.name, param.name, 'in', PSlotType.Data, param.type, param.defaultValue)),
            ...func.sign.returns.map(ret => new PSlot(ret.name, ret.name, 'out', PSlotType.Data, ret.type))
          ],
          typeTemplates: func.typeTemplates
        })
      }),
      ...this.extraNode
    ]
  }

  _exportNodes: PNode[] = []
  exportNodes(): PNode[] {
    if (this._exportNodes.length === 0) {
      const _getters = this.dataDeclares.map(x => PNode.getterFromTGD(x))
      const _setters = this.dataDeclares.map(x => PNode.globalSetterFromTGD(x))
      this._exportNodes = [...this.nodeDeclares, ..._getters, ..._setters]
    }
    
    return this._exportNodes
  }
}
